package edu.sjtu.grid.workflow.group;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import edu.sjtu.grid.workflow.Util;
import edu.sjtu.grid.workflow.facts.Facts;
import edu.sjtu.grid.workflow.facts.Job;
import edu.sjtu.grid.workflow.facts.Mapping;
import edu.sjtu.grid.workflow.facts.PC;
import edu.sjtu.grid.workflow.facts.Run;
import edu.sjtu.grid.workflow.graph.Node;

public class GroupNode implements Node{
	
	private Facts facts = null;
	
	private List<Job> jobList = null;
	
	private List<GroupNode> childList = null;
	private List<GroupNode> parentList = null;
	
	private Double upwardRank = null;
	private Double relativeLFT = null;
	private Map<Job, Double> relativeLFTMap = null;
	
	public List<Job> getJobList()
	{
		return this.jobList;
	}
	
	public GroupNode(Facts facts, List<Job> jobList)
	{
		this.facts = facts;
		this.jobList = jobList;
		this.childList = new ArrayList<GroupNode>();
		this.parentList = new ArrayList<GroupNode>();
		this.relativeLFTMap = new HashMap<Job, Double>();
	}
	
	public String toString()
	{
		StringBuilder s = new StringBuilder();
		
		s.append("[GroupNode]\t");
		
		s.append(Util.formatOutputDouble(this.getUpwardRank(), 2) 
				+ Util.formatOutputDouble(this.getLFT(), 2)
				+ "[");
		
		for(Job job : jobList)
			s.append(job.getIndex() + ",");
		s.append("]\n");
		
		return s.toString();
	}
	
	public Job getFirstJob() {
		return this.jobList.get(0);
	}
	
	public Job getLastJob() {
		return this.jobList.get(this.jobList.size() - 1);
	}
	
	public boolean isLastJob(Job job) {
		return this.getLastJob() == job;
	}


	public Job getNextJob(Job job) {
		if(job == null)
			return this.getFirstJob();
		for(int i=0; i<this.jobList.size()-1; i++)
		{
			if(this.jobList.get(i) == job)
				return this.jobList.get(i + 1);
		}
		return null;
	}
	
	public void addChild(GroupNode child)
	{
		if(this != child && !this.childList.contains(child))
			this.childList.add(child);
	}
	
	public void addParent(GroupNode parent)
	{
		if(this != parent && !this.parentList.contains(parent))
			this.parentList.add(parent);
	}
	
	public List<GroupNode> getChildList()
	{
		return childList;
	}
	
	public List<GroupNode> getParentList()
	{
		return parentList;
	}
	
	private double getAvgTransTime(GroupNode child)
	{
		return this.getLastJob().getAvgTransTime(child.getFirstJob());
	}
	protected double getMaxTransTime()
	{
		double max = 0;
		if(this.childList != null && !this.childList.isEmpty())
		{
			for(GroupNode child : this.childList)
			{
				double childUpwardRank = child.getUpwardRank();
				double avgTransTime = this.getAvgTransTime(child);
				double tmp =  childUpwardRank + avgTransTime;
				if(Util.isEqualOrBig(tmp, max))
					max = tmp;
			}
		}
		return max;
	}
	
	private double getFastestRunTime()
	{
		double sum = 0;
		for(Job job : this.jobList)
			sum += job.getFastestRunTime();
		return sum;
	}
	
	public double getMinEFT(Run run, Job job)
	{
		double eft = Double.MAX_VALUE;
		for(PC pc : this.facts.getPcs().getPcList())
			eft = Math.min(eft, this.getEFT(run, job, pc));
		return eft;
	}
	
	public double getEFT(Run run, Job job, PC pc)
	{
		double est = run.getEST(pc, this.getFirstJob());
		for(int i=0; i<this.jobList.size(); i++)
		{
			est += this.jobList.get(i).getFastestRunTime();
			if(this.jobList.get(i) == job)
				return est;
		}
		return 0;
	}
	
	public double getLFT(Job job)
	{
		Double lft = this.relativeLFTMap.get(job);
		return lft == null ? null : lft + this.facts.getGroupGraph().getDeadline();
	}
	
	private double getRelativeLFT()
	{
		if(this.relativeLFT == null)
		{
			if(this.getChildList().isEmpty())
				this.relativeLFT = (double)0;
			else
			{
				this.relativeLFT = (double)0;
				for(GroupNode childGroup : this.getChildList())
				{
					double tmp = childGroup.getRelativeLFT() - childGroup.getFastestRunTime();
					this.relativeLFT = Math.min(this.relativeLFT, tmp);
				}
			}
		}
		
		double lft = this.relativeLFT;
		for(int i=this.jobList.size()-1; i>=0; i--)
		{
			Job job = this.jobList.get(i);
			this.relativeLFTMap.put(job, lft);
			lft -= job.getFastestRunTime();
		}
		
		return this.relativeLFT;
	}
	
	public double getLFT()
	{
		return this.facts.getGroupGraph().getDeadline() + this.getRelativeLFT();
	}

	@Override
	public double getUpwardRank()
	{
		if(this.upwardRank == null)
		{
			double maxTransTime = this.getMaxTransTime();
			double avgRunTime = 0;
			for(int i=0; i<this.jobList.size(); i++)
			{
				Job f = this.jobList.get(i);
				avgRunTime += f.getAvgRunTime();
				if(i != this.jobList.size() - 1)
				{
					Job t = this.jobList.get(i+1);
					avgRunTime += f.getAvgTransTime(t);
				}
			}
			this.upwardRank = maxTransTime + avgRunTime;
		}
		return this.upwardRank;
	}
	
	private GroupMappingIterator getGroupMappingIterator(Job job, Run run) {
		
		List<GroupMapping> groupMappingList = new ArrayList<GroupMapping>();

		for(PC pc : this.facts.getPcs().getPcList())
		{
			Mapping fastest = run.getEarlistMapping_LFT(pc, job, this.getLFT());
			Mapping cheapest = run.getCheapestEarlistMapping(pc, job, this.getLFT());
			if(cheapest != null)
				groupMappingList.add(new GroupMapping(run, this, cheapest));
			if(fastest != null)
			{
				if(cheapest == null 
						|| Util.isSmall(fastest.getToTime(), cheapest.getToTime()))
					groupMappingList.add(new GroupMapping(run, this, fastest));
			}
		}
		Collections.sort(groupMappingList, new GroupMappingComparator());
		return new GroupMappingIterator(groupMappingList);
	}
	
	public GroupMappingIterator getGroupMappingIterator(Run run) {
		if(this.jobList.size() == 1)
			return this.getGroupMappingIterator(this.jobList.get(0), run);
		
		
		MDP mdp = new MDP(facts, this, run);
		return new GroupMappingIterator(mdp.getGroupMappingList());
	}
	
	private class GroupMappingComparator implements Comparator<GroupMapping>{
		
		@Override
		public int compare(GroupMapping arg0, GroupMapping arg1) {			
			double c0 = arg0.getCost();
			double c1 = arg1.getCost();
			if(Util.isEqual(c0, c1))
			{
				if(Util.isBig(arg0.getToTime(), arg1.getToTime()))
					return 1;
				else
					return -1;
			}
			else if(Util.isBig(c0, c1))
				return 1;
			else
				return -1;
		}
	}
}
